#include "dd_triangle.h"
#include "dd_context.h"
#include "dd_dynamic_array.h"
#include "dd_matrix.h"

#define edge_func(p1x, p1y, p2x, p2y, px, py) ( (p2x -p1x) *(py -p1y) -(p2y -p1y) *(px -p1x) )

int get_min(int val1, int val2, int val3) {
        if (val1 < val2) {
                if (val1 < val3) {
                        return val1;
                }
                else {
                        return val3;
                }
        }
        else {
                if (val2 < val3) {
                        return val2;
                }
                else {
                        return val3;
                }
        }
}

int get_max(int val1, int val2, int val3) {
        if (val1 > val2) {
                if (val1 > val3) {
                        return val1;
                }
                else {
                        return val3;
                }
        }
        else {
                if (val2 > val3) {
                        return val2;
                }
                else {
                        return val3;
                }
        }
}

struct vec2 {
        float x, y;
};

struct dd_vec3 mat_mult_vec(float mat1[16], struct dd_vec3 vec) {
        struct dd_vec3 result;
        result.x = mat1[0]*vec.x + mat1[4]*vec.y
                + mat1[8]*vec.z + mat1[12]*1;
        result.y = mat1[1]*vec.x + mat1[5]*vec.y
                + mat1[9]*vec.z + mat1[13]*1;
        result.z = mat1[2]*vec.x + mat1[6]*vec.y
                + mat1[10]*vec.z + mat1[14]*1;
        return result;
}

void dd_triangle_draw(struct dd_triangle tri) {

	tri.v1.z /= 3;
        tri.v2.z /= 3;
        tri.v3.z /= 3;

        if (tri.v1.z <= 0) tri.v1.z = 1;
        if (tri.v2.z <= 0) tri.v2.z = 1;
        if (tri.v3.z <= 0) tri.v3.z = 1;

        /* transform triangle based on global matrix */
	float* global_matrix = dd_da_get(dd_cam, dd_cam->elements-1);
        struct dd_vec3 temp;
        temp = mat_mult_vec(global_matrix, tri.v1);
        tri.v1 = temp;
        temp = mat_mult_vec(global_matrix, tri.v2);
        tri.v2 = temp;
        temp = mat_mult_vec(global_matrix, tri.v3);
        tri.v3 = temp;

	 /* move from 3d world space to 2d world space
         * this is using weak-perspective
         * which means objects that are away from the viewer
         * will be smaller and closer to the center of the world
         */
        struct vec2 vv1, vv2, vv3;
        vv1.x = tri.v1.x/tri.v1.z;
        vv1.y = tri.v1.y/tri.v1.z;

        vv2.x = tri.v2.x/tri.v2.z;
        vv2.y = tri.v2.y/tri.v2.z;

        vv3.x = tri.v3.x/tri.v3.z;
        vv3.y = tri.v3.y/tri.v3.z;

        /* offset of the camera offset of the 2d world
         * this must be the top/left point of the desired point of view
         * coordinates in 2d world space
         * the below (default) coordinates, make the center of the 2d world space,
         * the point of view
         */
        int offsetX = +(dd_w/2);
        int offsetY = +(dd_h/2);

        /* From 2D space to Screen space */
        vv1.x += offsetX;
        vv1.y = offsetY -vv1.y;

        vv2.x += offsetX;
        vv2.y = offsetY -vv2.y;

        vv3.x += offsetX;
        vv3.y = offsetY -vv3.y;

        int fromX = get_min(vv1.x, vv2.x, vv3.x);
        int toX   = get_max(vv1.x, vv2.x, vv3.x);
        int fromY = get_min(vv1.y, vv2.y, vv3.y);
        int toY   = get_max(vv1.y, vv2.y, vv3.y);

        if (fromX < 0) {
                fromX = 0;
        }

        if (toX > dd_w) {
                toX = dd_w;
        }

        if (fromX > toX) {
                fromX = 0;
                toX = 0;
        }

//      if (fromY < 0) {
//              fromY = 0;
//      }
//
//      if (toY > h) {
//              toY = h;
//      }

        if (-toY > -fromY) {
                fromY = 0;
                toY = 0;
        }

//      printw("%d %d %d %d\n", fromX, toX, -fromY, -toY);

    /* Print map */
    int x, y;
    for (y = 0;  y < dd_h; y++) {
        for (x = 0; x < dd_w; x++) {

//              if (vv1.x == x && vv1.y == y) {
//                      printw("1");
//                      continue;
//              }
//
//              if (vv2.x == x && vv2.y == y) {
//                      printw("2");
//                      continue;
//              }
//
//              if (vv3.x == x && vv3.y == y) {
//                      printw("3");
//                      continue;
//              }
//
//              printw(" ");
//              continue;

                        /* check if point is on the "positive" side of all four edges,
                         * this means its inside the square
                         */
                        if (
                             edge_func(vv1.x, vv1.y, vv2.x, vv2.y, (x), y) <= 0
                          && edge_func(vv2.x, vv2.y, vv3.x, vv3.y, (x), y) <= 0
                          && edge_func(vv3.x, vv3.y, vv1.x, vv1.y, (x), y) <= 0) {
                                ((char*)dd_screen)[x +(y*dd_w)] = tri.color;
                                continue;
                        }
                }
        }
}

void dd_vertex_draw(int vcount, float *v) {

	vcount *= 3;
	
	int i;
	for (i = 0; i < vcount; i += 9) {

		struct dd_vec3 v1, v2, v3;
		v1.x = v[i+0];
		v1.y = v[i+1];
		v1.z = v[i+2];

		v2.x = v[i+3];
		v2.y = v[i+4];
		v2.z = v[i+5];

		v3.x = v[i+6];
		v3.y = v[i+7];
		v3.z = v[i+8];

		/* if a vertex in the triangle is behind the camera, ignore triangle */
		if (v1.z <= 0 || v2.z <= 0 || v3.z <= 0) {
			continue;
		}

        /* transform triangle based on global matrix */
		v1 = mat_mult_vec(dd_global_matrix, v1);
		v2 = mat_mult_vec(dd_global_matrix, v2);
		v3 = mat_mult_vec(dd_global_matrix, v3);

		/* move from 3d world space to 2d world space
		* this is using weak-perspective
		* which means objects that are away from the viewer
		* will be smaller and closer to the center of the world
		*/
        struct vec2 vv1, vv2, vv3;
        vv1.x = v1.x/v1.z;
        vv1.y = v1.y/v1.z;

        vv2.x = v2.x/v2.z;
        vv2.y = v2.y/v2.z;

        vv3.x = v3.x/v3.z;
        vv3.y = v3.y/v3.z;

        /* offset of the camera offset of the 2d world
         * this must be the top/left point of the desired point of view
         * coordinates in 2d world space
         * the below (default) coordinates, make the center of the 2d world space,
         * the point of view
         */
        int offsetX = +(dd_w/2);
        int offsetY = +(dd_h/2);

        /* From 2D space to Screen space */
        vv1.x += offsetX;
        vv1.y = offsetY -vv1.y;

        vv2.x += offsetX;
        vv2.y = offsetY -vv2.y;

        vv3.x += offsetX;
        vv3.y = offsetY -vv3.y;


		/* Print map */
		int x, y;
		for (y = 0; y < dd_h; y++) {
		for (x = 0; x < dd_w; x++) {
		
			/* check if point is on the "positive" side of all four edges,
			* this means its inside the square
			*/
			if (
				edge_func(vv1.x, vv1.y, vv2.x, vv2.y, (x), y) <= 0
				&& edge_func(vv2.x, vv2.y, vv3.x, vv3.y, (x), y) <= 0
				&& edge_func(vv3.x, vv3.y, vv1.x, vv1.y, (x), y) <= 0) {
					((char*)dd_screen)[x +(y*dd_w)] = 6;
					continue;
			}
		}
		}
	}
}
